using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Diagnostics;
using Xeml.Visualization;
using System.Windows.Forms;
using Xeml.Document;
using Mpi.Common;

namespace Xeml.Visualization.Controls
{
    /// <summary>
    /// A custom control for visualizing a time scale of multiple time regions. 
    /// </summary>
    public partial class MultiTimeLineScale
    {
        #region Fields
        const int mainLineY = 20;

        private TimeRegionPixelMapper pxMapper;
        //private TimeDash timeDash;
        private int rowHeaderWidth = 50;

        private Rectangle mainArea;
        private Rectangle rowHeaderArea;
        private Rectangle allOverArea = new Rectangle(0, 0, 50, 90);


        private GraphicsPath mainLine;
        private List<Slider> slider;
        private int activeSlider = -1;
        private bool activeTimeEditor = false;
        private bool highLightTimeEditor = false;
        private Rectangle currentTimeDisplay;
        private Rectangle currentTimeDisplayInvalidate;
        private Rectangle currentZoomDisplay;
        private string currentTime;
        private string currentZoomFactor = "50";

        private int currentXPosition;

        private TimeEditor timeEditor;
        private ITimeObject timeObject;

        #endregion

        #region Properties

        private bool HighLightTimeEditor
        {
            get
            {
                return this.highLightTimeEditor;
            }
            set
            {
                if (this.highLightTimeEditor != value)
                {
                    this.highLightTimeEditor = value;
                    SetupTimeDisplay();
                }
            }
        }

        /// <summary>
        /// The widht of the row header.
        /// </summary>
        public int RowHeaderWidth
        {
            get { return rowHeaderWidth; }
            set { rowHeaderWidth = value; }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Setups the time scale for a given pixel mapper.
        /// </summary>
        /// <param name="pxMapper">The corresponding pixel mapper.</param>
        internal void Setup(TimeRegionPixelMapper pxMapper)
        {
            if (this.pxMapper != pxMapper) //do only if the new pxmapper differs from the currentS
            {
                if (pxMapper != null)
                {
                    pxMapper.Clearing -= new EventHandler(pxMapper_Clearing);
                    pxMapper.SplittingChanged -= new EventHandler(pxMapper_SplittingChanged);
                    pxMapper.SplitAdded -= new EventHandler<SplittingEventArgs>(pxMapper_SplitAdded);
                    pxMapper.SplitRemoved -= new EventHandler<SplittingEventArgs>(pxMapper_SplitRemoved);
                    pxMapper.LengthChanged -= new EventHandler<Mpi.Common.PropertyHasChangedEventArgs<TimeSpan>>(pxMapper_LengthChanged);
                }
                this.pxMapper = pxMapper;

                if (pxMapper != null)
                {
                    pxMapper.Clearing += new EventHandler(pxMapper_Clearing);
                    pxMapper.SplittingChanged += new EventHandler(pxMapper_SplittingChanged);
                    pxMapper.SplitAdded += new EventHandler<SplittingEventArgs>(pxMapper_SplitAdded);
                    pxMapper.SplitRemoved += new EventHandler<SplittingEventArgs>(pxMapper_SplitRemoved);
                    pxMapper.LengthChanged += new EventHandler<Mpi.Common.PropertyHasChangedEventArgs<TimeSpan>>(pxMapper_LengthChanged);
                    //pxMapper.Regions.RegionRemoved += new EventHandler<RegionRemovedEventArgs>(Regions_RegionRemoved);
                }

            }

            //this.AdjustSize();
            this.CalculateDrawAreas();
            InitDrawTools();
            InitSlider();
        }

        void pxMapper_LengthChanged(object sender, Mpi.Common.PropertyHasChangedEventArgs<TimeSpan> e)
        {
            //this.AdjustSize();
            this.CalculateDrawAreas();
            Invalidate();
            Debug.WriteLine("MultiTimLinScale: Length changed");
        }

        void pxMapper_Clearing(object sender, EventArgs e)
        {
            foreach (Slider s in this.slider)
            {
                s.Disable();
            }
            this.slider.Clear();
        }

        void pxMapper_SplitRemoved(object sender, SplittingEventArgs e)
        {
            Slider toRem = null;
            foreach (Slider sl in this.slider)
            {
                if (sl.Region == e.TimeRegion)
                    toRem = sl;
            }
            if (toRem != null)
            {
                toRem.Disable();
                this.slider.Remove(toRem);
            }
            Invalidate();

        }

        void pxMapper_SplitAdded(object sender, SplittingEventArgs e)
        {
            Slider sl = new Slider(this.pxMapper, e.TimeRegion, 3, this.Height - 6);
            sl.XOffset = this.rowHeaderWidth;
            slider.Add(sl);
            Invalidate();
        }




        void pxMapper_SplittingChanged(object sender, EventArgs e)
        {
            //this.AdjustSize();
            this.CalculateDrawAreas();
            //InitSlider();
            Invalidate();
        }

        /// <summary>
        /// Activates the time editor.
        /// </summary>
        /// <param name="time">The original time point for the editor.</param>
        /// <param name="min">The minimal value for the time editing process.</param>
        /// <param name="max">The maximal value for the time editing process.</param>
        public void ActivateTimeEditor(TimeSpan time, TimeSpan min, TimeSpan max, ITimeObject timeObject)
        {
            DeactivateTimeEditor(false);
            if (this.pxMapper != null)
            {
                this.timeObject = timeObject;
                this.timeEditor = new TimeEditor(time, pxMapper, min, max, timeObject);
                this.timeEditor.Y = this.mainArea.Top;
                this.timeEditor.Height = this.mainArea.Bottom - mainArea.Top;
                this.timeEditor.XOffset = rowHeaderWidth;
                this.timeEditor.TimeChanged += new EventHandler(timeEditor_TimeChanged);
                this.timeObject.TimePointHasChanged += new EventHandler<Mpi.Common.PropertyHasChangedEventArgs<TimeSpan>>(timeObject_TimePointHasChanged);
                this.currentTime = timeEditor.OriginalTime.ToString();
                Invalidate();

            }
        }

        void timeObject_TimePointHasChanged(object sender, Mpi.Common.PropertyHasChangedEventArgs<TimeSpan> e)
        {
            Invalidate();
        }



        /// <summary>
        /// Deactivates the time editor.
        /// </summary>
        public void DeactivateTimeEditor(bool invalidate)
        {
            activeTimeEditor = false;
            _firstMove = true;
            if (this.timeObject != null)
            {
                this.timeEditor.TimeChanged -= new EventHandler(timeEditor_TimeChanged);
                this.timeObject.TimePointHasChanged -= new EventHandler<Mpi.Common.PropertyHasChangedEventArgs<TimeSpan>>(timeObject_TimePointHasChanged);
            }
            this.timeObject = null;
            this.timeEditor = null;

            if (invalidate)
                Invalidate();
        }


        private int _startX;
        private bool _firstMove = true;
        public void MoveTimeEditor(int x)
        {
            this.currentXPosition = x + this.rowHeaderWidth;

            Debug.WriteLine("MTLS: move: "  +x);
            if (_firstMove)
            {
                Debug.WriteLine("MTLS: first move = true");
                _startX = x;
                _firstMove = false;
            }
            if (this.timeEditor != null)
            {
                Debug.WriteLine("MTLS: move: " + x);
                Debug.WriteLine("MTLS: _startX: " + _startX);
                if (!activeTimeEditor && Math.Abs(_startX - x) > 5)
                {
                    Debug.WriteLine("MTLS: activate timeeditor");
                    activeTimeEditor = true;
                }
                if (activeTimeEditor)
                {
                    this.timeEditor.X = x;
                    Invalidate(currentTimeDisplayInvalidate);
                }

            }
        }

        public void AcceptTimeEdit()
        {
            if (this.timeEditor != null)
            {

                activeTimeEditor = false;
                _firstMove = true;
                Debug.WriteLine("MTLS: first move = false");
                if (this.timeEditor.OriginalTime != this.timeEditor.CurrentTime)
                    OnEditTimeChanged(this.timeEditor.OriginalTime, this.timeEditor.CurrentTime);


                Invalidate();
            }
        }

        /// <summary>
        /// Adjusts the the size of this control.
        /// </summary>
        private void AdjustSize()
        {
            //int w = 0;
            //if (pxMapper != null)
            //{
            //    w = pxMapper.MapPoint(pxMapper.Regions.Length, false);
            //}

            this.Width = this.allOverArea.Width;
            //this.AutoScrollMinSize = new Size(this.allOverArea.Width,this.Height-20);

            //base.Width = w + this.rowHeaderWidth + 1 + 19; //19 = correct facto for XemlViewer
            //base.Height = 90;
        }

        /// <summary>
        /// Calculates the rowheader area and the main area.
        /// </summary>
        private void CalculateDrawAreas()
        {
            this.rowHeaderArea = new Rectangle(0, 0, this.rowHeaderWidth, this.Height - 20);
            //this.mainArea = new Rectangle(this.rowHeaderWidth, 0, Math.Max(this.Width - this.rowHeaderWidth - 1, 1), this.Height - 1);

            int w = 0;
            if (pxMapper != null)
            {
                w = pxMapper.MapPoint(pxMapper.Regions.Length);
            }
            this.mainArea = new Rectangle(this.rowHeaderWidth, 3, Math.Max(w, 1), Math.Max(this.Height - 7, 1));
            this.allOverArea = new Rectangle(0, 0, this.rowHeaderArea.Width + this.mainArea.Width, this.Height - 1);


            // AdjustSize();

            //mainLine.Reset();
            //mainLine.AddLine(0, Math.Max(this.Height - 1, 1), this.Width, Math.Max(this.Height - 1, 1));


        }

        /// <summary>
        /// Calculates a necessaries sliders and it's positions.
        /// </summary>
        private void InitSlider()
        {
            this.slider.Clear();
            if (this.pxMapper != null)
            {
                foreach (TimeRegion tr in this.pxMapper.Regions)
                {
                    Slider sl = new Slider(this.pxMapper, tr, 3, this.Height - 6);
                    sl.XOffset = this.rowHeaderWidth;
                    slider.Add(sl);
                }
            }
        }

        /// <summary>
        /// Checks whether or not a slider is on the given position.
        /// </summary>
        /// <param name="x">The x coordinate.</param>
        /// <param name="y">The y coordinate.</param>
        /// <param name="thickness">The thickness of the area around the coordinates which wiil be include in the hit test.</param>
        /// <returns>The indedx of the catched slider. -1 if no slider was catched.</returns>
        private int HitTestSlider(int x, int y, int thickness)
        {
            Pen p = new Pen(Color.Black, (float)thickness);
            for (int i = 0; i < slider.Count; i++)
            {
                if (slider[i].Line.IsOutlineVisible(x, y, p))
                    return i;
            }
            return -1;
        }

        /// <summary>
        /// Checks whether or not the time editor is on the given position.
        /// </summary>
        /// <param name="x">The x coordinate.</param>
        /// <param name="y">The y coordinate.</param>
        /// <returns></returns>
        private bool HitTestTimeEditor(int x, int y)
        {
            if (this.timeEditor != null)
            {
                return this.timeEditor.Shape.IsVisible(x, y);
            }
            return false;
        }


        /// <summary>
        /// An event handler for time changing of an time editor.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void timeEditor_TimeChanged(object sender, EventArgs e)
        {
            this.currentTime = (sender as TimeEditor).CurrentTime.ToString();
            SetupTimeDisplay();
            OnEditTimeChanging((sender as TimeEditor).OriginalTime, (sender as TimeEditor).CurrentTime);
        }

        /// <summary>
        /// Set the correct position for the time display area.
        /// </summary>
        private void SetupTimeDisplay()
        {

            Rectangle oldRect = this.currentTimeDisplay;
            //if (this.currentXPosition < this.rowHeaderWidth)
            //{
            //    //  this.currentTime = pxMapper.MapPoint(0).ToString();
            //    this.currentTimeDisplay.Width = TextRenderer.MeasureText(this.currentTime, this.currentTimeFont).Width;
            //    this.currentTimeDisplay.X = rowHeaderWidth;


            //}
            //else if (currentXPosition > this.Width)
            //{


            //    //   this.currentTime = pxMapper.MapPoint(this.Width).ToString();
            //    this.currentTimeDisplay.Width = TextRenderer.MeasureText(this.currentTime, this.currentTimeFont).Width;
            //    this.currentTimeDisplay.X = this.Width;


            //}
            //else
            //{
            //    // this.currentTime = pxMapper.MapPoint(currentXPosition - rowHeaderWidth).ToString();
            //    this.currentTimeDisplay.Width = TextRenderer.MeasureText(this.currentTime, this.currentTimeFont).Width;


            //    if (currentXPosition > this.Width - currentTimeDisplay.Width)
            //    {
            //        this.currentTimeDisplay.X = this.Width - currentTimeDisplay.Width;
            //    }
            //    else
            //    {
            //        this.currentTimeDisplay.X = currentXPosition;
            //    }


            //}
            currentTimeDisplayInvalidate = Rectangle.Union(oldRect, currentTimeDisplay);
            if (this.timeEditor != null)
                currentTimeDisplayInvalidate = Rectangle.Union(currentTimeDisplayInvalidate, timeEditor.InvalidateArea);
            currentTimeDisplayInvalidate.X--;
            currentTimeDisplayInvalidate.Width += 2;

        }
        #endregion

        #region Events

        public event EventHandler<TimeChangeEventArgs> EditTimeChanging;
        public event EventHandler<TimeChangeEventArgs> EditTimeChanged;


        /// <summary>
        /// Event handling method if the edit time is changing.
        /// </summary>
        /// <param name="oldTime">The old time.</param>
        /// <param name="newTime">The new time.</param>
        protected virtual void OnEditTimeChanging(TimeSpan oldTime, TimeSpan newTime)
        {
            this.Invalidate(currentTimeDisplayInvalidate);
            if (this.EditTimeChanging != null)
            {
                TimeChangeEventArgs tce = new TimeChangeEventArgs(oldTime, newTime, timeObject);
                this.EditTimeChanging(this, tce);
            }
        }

        /// <summary>
        /// Event handling method if the edit time is changed.
        /// </summary>
        /// <param name="oldTime">The old time.</param>
        /// <param name="newTime">The new time.</param>
        protected virtual void OnEditTimeChanged(TimeSpan oldTime, TimeSpan newTime)
        {
            Debug.WriteLine("MouseUp");
            if (this.EditTimeChanged != null)
            {
                this.timeEditor.AcceptChange();
                TimeChangeEventArgs tce = new TimeChangeEventArgs(oldTime, newTime, timeObject);
                this.EditTimeChanged(this, tce);
            }
        }



        #endregion
    }

    /// <summary>
    /// Event arguments for a time cahnging event.
    /// </summary>
    public class TimeChangeEventArgs : EventArgs
    {
        #region Fields
        private TimeSpan oldTime;
        private TimeSpan newTime;
        private object timeObject;
        #endregion


        #region Properties

        /// <summary>
        /// The related edit object.
        /// </summary>
        public object TimeObject
        {
            get { return timeObject; }
        }

        /// <summary>
        /// The new time.
        /// </summary>
        public TimeSpan NewTime
        {
            get { return newTime; }
        }

        /// <summary>
        /// The old time.
        /// </summary>
        public TimeSpan OldTime
        {
            get { return oldTime; }
        }

        /// <summary>
        /// The difference between the old and the new time.
        /// </summary>
        public TimeSpan Diff
        {
            get
            {
                return newTime - oldTime;
            }
        }
        #endregion

        #region Constructors
        /// <summary>
        /// The parameterized constructor.
        /// </summary>
        /// <param name="oldTime">The old time.</param>
        /// <param name="newTime">The new time.</param>
        public TimeChangeEventArgs(TimeSpan oldTime, TimeSpan newTime, object timeObject)
        {
            this.timeObject = timeObject;
            this.oldTime = oldTime;
            this.newTime = newTime;
        }
        #endregion
    }

    /// <summary>
    /// Represents a slider on a time scale direct linked to a time region.
    /// </summary>
    internal class Slider
    {
        #region Fields
        private GraphicsPath line;
        private int x;
        private float height;
        private TimeRegion region;
        private int leftWall;
        private int rightWall;
        private int xOffset = 10;
        private TimeRegionPixelMapper _pxMapper;
        #endregion

        #region Properties
        /// <summary>
        /// The offset in the x coordinate.
        /// </summary>
        public int XOffset
        {
            get { return xOffset; }
            set
            {
                xOffset = value;
                this.line = new GraphicsPath();
                this.line.AddLine(xOffset + region.EndPx, _y, xOffset + region.EndPx, height + _y);
                SetupWalls();
            }
        }

        /// <summary>
        /// The preview area if the slider is in zoom mode.
        /// </summary>
        public RectangleF PreviewZoomArea
        {
            get
            {
                RectangleF ret = new RectangleF();
                ret.X = this.region.StartPx + xOffset;
                ret.Y = _y;

                ret.Height = this.height;
                ret.Width = this.x - this.region.StartPx - xOffset;
                return ret;
            }
        }

        /// <summary>
        /// Indicates the direction of zooming, -1 for zoom in, +1 for zoom out.
        /// </summary>
        public int MoveDirection
        {
            get
            {
                if (x <= region.EndPx + xOffset)
                    return -1;
                else
                    return +1;
            }
        }

        /// <summary>
        /// The the min x-coordinate for the slider position.
        /// </summary>
        public float LeftWall
        {
            get { return leftWall + xOffset; }
        }

        /// <summary>
        /// The the max x-coordinate for the slider position.
        /// </summary>
        public float RightWall
        {
            get { return rightWall + xOffset; }
        }

        /// <summary>
        /// The assigned time region.
        /// </summary>
        public TimeRegion Region
        {
            get { return region; }
            //set { region = value; }
        }

        /// <summary>
        /// The current x-coordinate without offset.
        /// </summary>
        public int X
        {
            get
            {
                return x;

            }
            set
            {
                if (value < x)
                    x = Math.Max(value, leftWall);
                else
                    x = Math.Min(value, rightWall);

                Debug.WriteLine("set X to: " + x);
            }
        }

        /// <summary>
        /// The visual representation of this slider.
        /// </summary>
        public GraphicsPath Line
        {
            get { return line; }
        }

        /// <summary>
        /// A visual representation if the slider is in zoom mode.
        /// </summary>
        public GraphicsPath PreviewLine
        {
            get
            {
                GraphicsPath ret = new GraphicsPath();
                ret.AddLine(this.x, _y, this.x, height + _y);
                return ret;
            }
        }

        private int _y;

        public int Y
        {
            get { return _y; }
        }

        #endregion

        #region Constructors
        /// <summary>
        /// The parameterized constructor.
        /// </summary>
        /// <param name="tr">The corresponding time region.</param>
        /// <param name="height">The height of the slider.</param>
        public Slider(TimeRegionPixelMapper pxMapper, TimeRegion tr, int y, int height)
        {
            _pxMapper = pxMapper;
            Debug.WriteLine("Slider: Constructor with Region " + tr.Length);
            _y = y;
            this.height = height;
            this.line = new GraphicsPath();
            this.X = tr.EndPx;

            this.line.AddLine(this.X, _y, this.X, height);
            this.region = tr;
            LinkedListNode<TimeRegion> lln = this.region._collection.Find(region);
            if (lln.Next != null)
            {
                lln.Next.Value.ResolutionChanged += new EventHandler<ResolutionEventArgs>(OnNeighborsChanged);
                lln.Next.Value.LengthHasChanged += new EventHandler<Mpi.Common.PropertyHasChangedEventArgs<TimeSpan>>(OnNeighborsLengthChanged);
            }
            if (lln.Previous != null)
            {
                lln.Previous.Value.ResolutionChanged += new EventHandler<ResolutionEventArgs>(OnNeighborsChanged);
                lln.Previous.Value.LengthHasChanged += new EventHandler<Mpi.Common.PropertyHasChangedEventArgs<TimeSpan>>(OnNeighborsLengthChanged);
            }

            SetupWalls();

            tr.ResolutionChanged += new EventHandler<ResolutionEventArgs>(OnRegionChanged);
            tr.LengthHasChanged += new EventHandler<PropertyHasChangedEventArgs<TimeSpan>>(OnRegionLengthHasChanged);
        }



        #endregion

        #region Methods
        /// <summary>
        /// Applies the changes at the end of an zoom activity.
        /// </summary>
        public void ApplyChange()
        {
            if (x - this.region.StartPx - xOffset > 0)
            {
                region.ResolutionChanged -= new EventHandler<ResolutionEventArgs>(OnRegionChanged);
                region.Collection.SuspendChangeEvent();
                int diff = region.PxLength;
                this.region.PxLength = x - this.region.StartPx - xOffset;
                diff = diff - this.region.PxLength;
                LinkedListNode<TimeRegion> lln = this.region._collection.Find(region);
                if (lln.Next != null)
                    lln.Next.Value.PxLength += diff;
                SetupWalls();
                region.Collection.ResumeChangeEvent(true);
                region.ResolutionChanged += new EventHandler<ResolutionEventArgs>(OnRegionChanged);
                OnRegionChanged(null, null);
            }

        }


        /// <summary>
        /// Calculates zoom factor which will set.
        /// </summary>
        /// <returns>The calculated value.</returns>
        public int PreviewZoomFactor()
        {

            long pxLength = x - this.region.StartPx - xOffset;
            pxLength = pxLength / this.region.LogicPixelSize;
            double resolution = (double)this.region.Length.TotalMinutes / (double)pxLength;
            return (int)(((double)_pxMapper.StandarResolution / resolution) * 100);
        }


        /// <summary>
        /// Calculates the walls of the slider.
        /// </summary>
        private void SetupWalls()
        {
            LinkedListNode<TimeRegion> lln = region.Collection.Find(region);
            if (lln.Previous != null)
                leftWall = xOffset + lln.Previous.Value.EndPx + 5;
            else
                this.leftWall = xOffset + 5;

            if (lln.Next != null)
                rightWall = xOffset + lln.Next.Value.EndPx - 5;
            else
                rightWall = 999999;
        }

        /// <summary>
        /// Event handling if the assigned time region is changed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnRegionChanged(object sender, ResolutionEventArgs e)
        {
            this.line.Reset();// = new GraphicsPath();
            this.line.AddLine(xOffset + region.EndPx, _y, xOffset + region.EndPx, height + _y);
        }

        /// <summary>
        /// Event handling if the assigned time region is changed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnRegionLengthHasChanged(object sender, PropertyHasChangedEventArgs<TimeSpan> e)
        {
            this.line = new GraphicsPath();
            this.line.AddLine(xOffset + region.EndPx, _y, xOffset + region.EndPx, height + _y);
        }



        /// <summary>
        /// Event handling if the succesor or predecessor region is changed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnNeighborsChanged(object sender, ResolutionEventArgs e)
        {
            if (!disabled)
                SetupWalls();
        }

        void OnNeighborsLengthChanged(object sender, Mpi.Common.PropertyHasChangedEventArgs<TimeSpan> e)
        {
            if (!disabled)
                SetupWalls();
        }
        #endregion


        private bool disabled = false;
        internal void Disable()
        {
            this.disabled = true;
            this.region.ResolutionChanged -= new EventHandler<ResolutionEventArgs>(OnRegionChanged);
            this.region.LengthHasChanged -= new EventHandler<PropertyHasChangedEventArgs<TimeSpan>>(OnNeighborsLengthChanged);
        }
    }

    /// <summary>
    /// Represent an object which can be used for time editing in a graphical way.
    /// </summary>
    internal class TimeEditor
    {
        #region Fields
        private int leftWall;
        private int rightWall;
        private TimeSpan min;
        private TimeSpan max;
        private int xOffset = 10;
        private GraphicsPath shape;
        private Rectangle invalidateArea;
        private TimeSpan currentTime;
        private TimeSpan originalTime;
        private TimeRegionPixelMapper pxMapper;

        private int x;
        private int y;

        private ITimeObject timeObject;

        #endregion

        #region Properties
        /// <summary>
        /// The y coordinate.
        /// </summary>
        public int Y
        {
            get { return y; }
            set
            {
                y = value;
                CalculateShape();
            }
        }


        /// <summary>
        /// The x offset of the control.
        /// </summary>
        public int XOffset
        {
            get { return xOffset; }
            set
            {
                xOffset = value;
                CalculateShape();
            }
        }

        private int _height;

        public int Height
        {
            get { return _height; }
            set { _height = value; CalculateShape(); }
        }


        /// <summary>
        /// The x coordinate.
        /// </summary>
        public int X
        {
            get { return x; }
            set
            {
                SetXPosition(value, true);
            }
        }

        private void SetXPosition(int value, bool raiseTimeChangeEvent)
        {

            int newPx = FindNextPixel(x, value);
            if (newPx != x)
            {
                x = newPx;
                //find the next good pixel
                this.currentTime = pxMapper.MapPoint(x);
                CalculateShape();
                if (raiseTimeChangeEvent)
                {
                    this.timeObject.TimePointHasChanged -= new EventHandler<Mpi.Common.PropertyHasChangedEventArgs<TimeSpan>>(timeObject_TimePointHasChanged);
                    OnTimeChanged();
                    this.timeObject.TimePointHasChanged += new EventHandler<Mpi.Common.PropertyHasChangedEventArgs<TimeSpan>>(timeObject_TimePointHasChanged);
                }

            }

        }

        /// <summary>
        /// The original time of the editor.
        /// </summary>
        public TimeSpan OriginalTime
        {
            get { return originalTime; }
        }


        /// <summary>
        /// The current time of the editor.
        /// </summary>
        public TimeSpan CurrentTime
        {
            get { return currentTime; }
            set
            {
                currentTime = value;
                this.X = pxMapper.MapPoint(currentTime);

            }
        }

        /// <summary>
        /// The graphical representation of the editor.
        /// </summary>
        public GraphicsPath Shape
        {
            get { return shape; }
        }

        /// <summary>
        /// The invlidated graphical representation of the editor.
        /// </summary>
        public Rectangle InvalidateArea
        {
            get { return invalidateArea; }
        }

        #endregion

        #region Constructors
        /// <summary>
        /// A parameterized constructor.
        /// </summary>
        /// <param name="originalTime">The initial time for the constructor.</param>
        /// <param name="pxMapper">A pixel mapper object of the used time scale.</param>
        public TimeEditor(TimeSpan originalTime, TimeRegionPixelMapper pxMapper, ITimeObject timeObject)
            : this(originalTime, pxMapper, TimeSpan.Zero, pxMapper.Regions.Length, timeObject)
        {


        }

        /// <summary>
        /// A parameterized constructor.
        /// </summary>
        /// <param name="originalTime">The initial time for the constructor.</param>
        /// <param name="pxMapper">A pixel mapper object of the used time scale.</param>
        /// <param name="min">The minimal time for the editor.</param>
        /// <param name="max">The maximal time for the editor.</param>
        public TimeEditor(TimeSpan originalTime, TimeRegionPixelMapper pxMapper, TimeSpan min, TimeSpan max, ITimeObject timeObject)
        {
            this.timeObject = timeObject;
            this.shape = new GraphicsPath();
            this.min = min;
            this.max = max;
            this.pxMapper = pxMapper;
            this.pxMapper.Regions.Changed += new EventHandler(Regions_Changed);
            this.originalTime = originalTime;

            SetupWalls();
            this.CurrentTime = originalTime;

            this.timeObject.TimePointHasChanged += new EventHandler<Mpi.Common.PropertyHasChangedEventArgs<TimeSpan>>(timeObject_TimePointHasChanged);

        }

        void timeObject_TimePointHasChanged(object sender, Mpi.Common.PropertyHasChangedEventArgs<TimeSpan> e)
        {
            this.originalTime = e.NewValue;
            this.currentTime = e.NewValue;
            SetXPosition(pxMapper.MapPoint(currentTime), false);

        }

        #endregion

        #region Methods

        /// <summary>
        /// Accepts the change and sets the original time to the current time.S
        /// </summary>
        public void AcceptChange()
        {
            this.originalTime = this.currentTime;
        }

        /// <summary>
        /// Finds the next reasonable pixel.
        /// </summary>
        /// <param name="oldPx">The last x position.</param>
        /// <param name="newPx">The current x position.</param>
        /// <returns>The next reasonable x position.</returns>
        private int FindNextPixel(int oldPx, int newPx)
        {
            if (newPx < leftWall)
                return this.leftWall;
            if (newPx > rightWall)
                return this.rightWall;

            int nxtPx = pxMapper.LogicPixelAddressOf(newPx); //(newPx / (int)pxMapper.LogicPxSize) * (int)pxMapper.LogicPxSize;
            Debug.WriteLine("TimeEditor: set physical px from " + newPx + " to " + nxtPx);
            return nxtPx;
            //return this.pxMapper.MapPoint(pxMapper.MapPoint(newPx));
            //if (pxMapper.MapPoint(newPx) > TimeSpan.Zero)
            //    return newPx;
            //else
            //{
            //if (oldPx < newPx)
            //{
            //    for (int i = newPx; i <= pxMapper.MapPoint(pxMapper.Regions.Length); i++)
            //    {
            //        if (pxMapper.MapPoint(i) > TimeSpan.Zero)
            //        {
            //            nxtPx = i;
            //            break;
            //        }
            //    }
            //}

            //else if (oldPx > newPx)
            //    for (int i = newPx; i >= 0; i--)
            //    {
            //        if (pxMapper.MapPoint(i) >= TimeSpan.Zero)
            //        {
            //            nxtPx = i;
            //            break;
            //        }
            //    }

            ////Debug.WriteLine("oldPx:" + oldPx);
            ////Debug.WriteLine("newPx:" + newPx);
            ////Debug.WriteLine("nxtPx:" + nxtPx);
            //if (Math.Abs(nxtPx - newPx) < Math.Abs(newPx - oldPx))
            //    return nxtPx;
            //else
            //    return oldPx;

            //}
        }

        /// <summary>
        /// Calculates the x barriers for the position.
        /// </summary>
        private void SetupWalls()
        {
            this.leftWall = pxMapper.MapPoint(this.min);
            this.rightWall = pxMapper.MapPoint(this.max);
        }

        /// <summary>
        /// Event handling if the time regions is changed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Regions_Changed(object sender, EventArgs e)
        {
            this.x = pxMapper.MapPoint(this.currentTime);
            CalculateShape();
            SetupWalls();
        }

        /// <summary>
        /// Calculates the current shape for the editor.
        /// </summary>
        private void CalculateShape()
        {

            RectangleF oldBounds = this.shape.GetBounds();

            this.shape.Reset();
            //this.shape.AddRectangle(new Rectangle(x + xOffset - 3, y + 2, 6, _height - 4));
            this.shape.AddLine(x + xOffset, y, x + xOffset, _height + y);

            RectangleF newBounds = this.shape.GetBounds();
            invalidateArea = Rectangle.Union(Rectangle.Round(oldBounds), Rectangle.Round(newBounds));
            invalidateArea.X--;
            invalidateArea.Width += 2;
            invalidateArea.Y--;
            invalidateArea.Height += 2;

        }

        #endregion

        #region Events
        /// <summary>
        /// Fires the time changed event. 
        /// </summary>
        protected virtual void OnTimeChanged()
        {
            //if (this.originalTime != this.currentTime)
            //{
            if (TimeChanged != null)
                TimeChanged(this, new EventArgs());
            //}
        }


        /// <summary>
        /// An event if the current time is changed.
        /// </summary>
        public event EventHandler TimeChanged;

        #endregion

    }
}
